/*
 *  WsvJdbc - a JDBC driver for WSV (values separated by whatever) data files
 *  Copyright (C) 2004  Daniele Pes
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
package axs.jdbc.dataSpecification;

import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import axs.jdbc.utils.StringUtilities;


/**
 * This class realizes the mapping among (SQL types + Java primitive types) and their relating (java wrapper/primitive type id consts).
 * It is useful to get Java types out of <code>java.sql.Types</code> and vice-versa. Here is an extension of data types as stated in 
 * <code>java.sql</code>, defined in order to take into consideration primitive types also.
 * It is used, e.g., to implement the ResultSet.getObject().
 *<table width="20%" border="1" align="center">
 *  <tr>     <td width="36%"><b>NAME</b></td>    <td width="50%"><b>Java type</b></td>    <td width="14%"><b>TYPE</b></td>  </tr>
 *  <tr>    <td width="36%">VARCHAR</td>    <td width="50%">String.class</td>    <td width="14%">12</td>  </tr>
 *  <tr>    <td width="36%">BOOLEAN</td>    <td width="50%">Boolean.class</td>    <td width="14%">16</td>  </tr>
 *  <tr>    <td width="36%">TINYINT</td>    <td width="50%">Byte.class</td>    <td width="14%">-6</td>  </tr>
 *  <tr>    <td width="36%">CHAR</td>    <td width="50%">Character.class</td>    <td width="14%">1</td>  </tr>
 *  <tr>    <td width="36%">DOUBLE</td>    <td width="50%">Double.class</td>    <td width="14%">8</td>  </tr>
 *  <tr>    <td width="36%">FLOAT</td>    <td width="50%">Float.class</td>    <td width="14%">6</td>  </tr>
 *  <tr>    <td width="36%">INTEGER</td>    <td width="50%">Integer.class</td>    <td width="14%">4</td>  </tr>
 *  <tr>    <td width="36%">BIGINT</td>    <td width="50%">Long.class</td>    <td width="14%">-5</td>  </tr>
 *  <tr>    <td width="36%">SMALLINT</td>    <td width="50%">Short.class</td>    <td width="14%">5</td>  </tr>
 *  <tr>    <td width="36%">TIMESTAMP</td>    <td width="50%">Timestamp.class</td>    <td width="14%">93</td>  </tr>
 *  <tr>    <td width="36%">DATE</td>    <td width="50%">Date.class</td>    <td width="14%">91</td>  </tr>
 *  <tr>    <td width="36%">TIME</td>    <td width="50%">Time.class</td>    <td width="14%">92</td>  </tr>
 *  <tr>    <td width="36%">VARBINARY</td>    <td width="50%">byte[].class</td>    <td width="14%">-3</td>  </tr>
 *  <tr>    <td width="36%">NULL</td>    <td width="50%">null</td>    <td width="14%">0</td>  </tr>
 *  <tr>    <td width="36%">JAVA_OBJECT</td>    <td width="50%">Object.class</td>    <td width="14%">2000</td>  </tr>
 *  <tr>    <td width="36%">P_BOOLEAN</td>    <td width="50%">boolean.class</td>    <td width="14%">100016</td>  </tr>
 *  <tr>    <td width="36%">P_TINYINT</td>    <td width="50%">byte.class</td>    <td width="14%">9994</td>  </tr>
 *  <tr>    <td width="36%">P_CHAR</td>    <td width="50%">char.class</td>    <td width="14%">100001</td>  </tr>
 *  <tr>    <td width="36%">P_DOUBLE</td>    <td width="50%">double.class</td>    <td width="14%">100008</td>  </tr>
 *  <tr>    <td width="36%">P_FLOAT</td>    <td width="50%">float.class</td>    <td width="14%">100006</td>  </tr>
 *  <tr>    <td width="36%">P_INTEGER</td>    <td width="50%">int.class</td>    <td width="14%">100004</td>  </tr>
 *  <tr>    <td width="36%">P_BIGINT</td>    <td width="50%">long.class</td>    <td width="14%">9995</td>  </tr>
 *  <tr>    <td width="36%">P_SMALLINT</td>    <td width="50%">short.class</td>    <td width="14%">100005</td>  </tr>
 *</table><br>
 * 
 * @author 		Daniele Pes
 * @version    $Id: SqlTypes.java,v 1.2 2007/04/16 17:24:37 montag451 Exp $
 */
public class SqlTypes{
			
	public final static int TYPE_ID_GAP = 10000;
	
   public final static int P_BOOLEAN  = java.sql.Types.BOOLEAN  + TYPE_ID_GAP;   	 	
	public final static int P_TINYINT  = java.sql.Types.TINYINT  + TYPE_ID_GAP;
	public final static int P_CHAR	  = java.sql.Types.CHAR     + TYPE_ID_GAP;
	public final static int P_DOUBLE	  = java.sql.Types.DOUBLE   + TYPE_ID_GAP;
	public final static int P_FLOAT	  = java.sql.Types.FLOAT    + TYPE_ID_GAP;
	public final static int P_INTEGER  = java.sql.Types.INTEGER  + TYPE_ID_GAP;
	public final static int P_BIGINT   = java.sql.Types.BIGINT   + TYPE_ID_GAP;
	public final static int P_SMALLINT = java.sql.Types.SMALLINT + TYPE_ID_GAP;
	
	
		
	// Names of customized types (used to get their equivalent classes). 'P' stands for Primitive type.	
	private static final String VARCHAR    	= "VARCHAR";
	private static final String BOOLEAN     	= "BOOLEAN";
	private static final String TINYINT     	= "TINYINT";
	private static final String CHAR        	= "CHAR";
	private static final String DOUBLE      	= "DOUBLE";
	private static final String FLOAT       	= "FLOAT";
	private static final String INTEGER     	= "INTEGER";
	private static final String BIGINT      	= "BIGINT";
	private static final String SMALLINT    	= "SMALLINT";
	private static final String SQL_DATE 		= "DATE";
	private static final String SQL_TIME      = "TIME";
	private static final String SQL_TIMESTAMP = "TIMESTAMP";		
	private static final String VARBINARY   	= "VARBINARY";
	private static final String NULL        	= "NULL";
	private static final String JAVA_OBJECT 	= "JAVA_OBJECT";
	
	// primitive types
	private static final String P__BOOLEAN    = "P_BOOLEAN";
	private static final String P__TINYINT    = "P_TINYINT";
	private static final String P__CHAR       = "P_CHAR";
	private static final String P__DOUBLE     = "P_DOUBLE";
	private static final String P__FLOAT      = "P_FLOAT";
	private static final String P__INTEGER    = "P_INTEGER";
	private static final String P__BIGINT     = "P_BIGINT";
	private static final String P__SMALLINT   = "P_SMALLINT";
		
	// names of SQL types
	private static final String SQL__VARCHAR     = "VARCHAR";
	private static final String SQL__BOOLEAN     = "BOOLEAN";
	private static final String SQL__TINYINT     = "TINYINT";
	private static final String SQL__CHAR 	      = "CHAR";
	private static final String SQL__DOUBLE      = "DOUBLE";
	private static final String SQL__FLOAT       = "FLOAT";
	private static final String SQL__INTEGER     = "INTEGER";
	private static final String SQL__BIGINT      = "BIGINT";
	private static final String SQL__SMALLINT    = "SMALLINT";	
	private static final String SQL__TIMESTAMP   = "TIMESTAMP";
	private static final String SQL__DATE        = "DATE";
	private static final String SQL__TIME        = "TIME";	
	private static final String SQL__VARBINARY   = "VARBINARY";
	private static final String SQL__NULL        = "NULL";
	private static final String SQL__JAVA_OBJECT = "JAVA_OBJECT";		
	
	private static HashMap sqlTypesToInt;
	private static HashMap sqlIntToTypes;
	private static HashMap sqlStringToTypes;
	private static HashMap sqlIntToSqlTypeNames;			

	private static void setMap(Class cls, 
										int   id){		
		sqlTypesToInt.put((Object)cls, new Integer(id));
	}
	

	private static void setMap(int   id, 
	                           Class cls){	                     
		sqlIntToTypes.put(new Integer(id), cls);
	}
	
	
	private static void setMap(int    id, 
										String sqlTypeName){		
		sqlIntToSqlTypeNames.put(new Integer(id), sqlTypeName);
	}
	
	
	private static void setMap(String typeName, 
										Class  cls){
		sqlStringToTypes.put(typeName, cls);
	}

	// types still to be considered
   //	public final static int BIT 					 =  -7;							
   //public final static int NUMERIC 				 =   2;
   //public final static int DECIMAL				 =   3;								
   //public final static int OTHER					 = 1111;
   //public final static int JAVA_OBJECT         = 2000;
   //public final static int DISTINCT            = 2001;
   //public final static int STRUCT              = 2002;
   //public final static int ARRAY               = 2003;
   //public final static int BLOB                = 2004;
   //public final static int CLOB                = 2005;
   //public final static int REF                 = 2006;        
   //public final static int DATALINK = 70;	
   
	static{
		sqlTypesToInt	      = new HashMap();
		sqlIntToTypes	      = new HashMap();
		sqlStringToTypes     = new HashMap();
		sqlIntToSqlTypeNames = new HashMap();		
		// sqlTypesToInt
		setMap(String.class,			 		java.sql.Types.VARCHAR);
		setMap(Boolean.class,		 		java.sql.Types.BOOLEAN);
		setMap(Byte.class,			 		java.sql.Types.TINYINT);
		setMap(Character.class,		 		java.sql.Types.CHAR);
		setMap(Double.class,			 		java.sql.Types.DOUBLE);
		setMap(Float.class,			 		java.sql.Types.FLOAT);
		setMap(Integer.class,		 		java.sql.Types.INTEGER);
		setMap(Long.class,          		java.sql.Types.BIGINT);
		setMap(Short.class,         		java.sql.Types.SMALLINT);
		setMap(java.sql.Date.class, 		java.sql.Types.TIMESTAMP);
		setMap(java.sql.Date.class, 		java.sql.Types.DATE);
		setMap(java.sql.Time.class, 		java.sql.Types.TIME);
		setMap(byte[].class,			 		java.sql.Types.VARBINARY);
		setMap(null,				    		java.sql.Types.NULL);
		setMap(Object.class,			 		java.sql.Types.JAVA_OBJECT);
		setMap(boolean.class,		 		P_BOOLEAN);
		setMap(byte.class,			 		P_TINYINT);
		setMap(char.class,			 		P_CHAR);
		setMap(double.class,			 		P_DOUBLE);
		setMap(float.class,			 		P_FLOAT);
		setMap(int.class,			    		P_INTEGER);
		setMap(long.class,			 		P_BIGINT);
		setMap(short.class,			 		P_SMALLINT);
		setMap(SqlCounterFakeType.class, java.sql.Types.INTEGER);
		// Note: sqlIntToTypes and sqlStringToTypes keep trace of data type as are defined by data type specification.
		//       To specify data types you can use the java.sql set of integers (here extended to keep into 
		//		   consideration primitive types) or the here defined set of strings. The set of Strings (type names) can 
		//			also passed from the outer scope: in this case those type identifiers will be used!		
		// sqlIntToTypes
		setMap(java.sql.Types.VARCHAR,	  String.class);
		setMap(java.sql.Types.BOOLEAN,	  Boolean.class);
		setMap(java.sql.Types.TINYINT,	  Byte.class);
		setMap(java.sql.Types.CHAR,		  Character.class);
		setMap(java.sql.Types.DOUBLE,		  Double.class);
		setMap(java.sql.Types.FLOAT,		  Float.class);
		setMap(java.sql.Types.INTEGER,	  Integer.class);
		setMap(java.sql.Types.BIGINT,	 	  Long.class);
		setMap(java.sql.Types.SMALLINT,	  Short.class);
		setMap(java.sql.Types.TIMESTAMP,   java.sql.Timestamp.class);
		setMap(java.sql.Types.DATE,   	  java.sql.Date.class);
		setMap(java.sql.Types.TIME,		  java.sql.Time.class);
		setMap(java.sql.Types.VARBINARY,	  byte[].class);
		setMap(java.sql.Types.NULL,		  (Class)null);
		setMap(java.sql.Types.JAVA_OBJECT, Object.class);		
		setMap(P_BOOLEAN,	  					  boolean.class);
		setMap(P_TINYINT,	  					  byte.class);
		setMap(P_CHAR,		  					  char.class);
		setMap(P_DOUBLE,		  				  double.class);
		setMap(P_FLOAT,		  				  float.class);
		setMap(P_INTEGER,	  					  int.class);
		setMap(P_BIGINT,		  				  long.class);
		setMap(P_SMALLINT,	  				  short.class);										
		//	sqlStringToTypes
		setMap(VARCHAR,		  		String.class);
		setMap(BOOLEAN, 		  		Boolean.class);
		setMap(TINYINT, 		  		Byte.class);
		setMap(CHAR	,	 		  		Character.class);
		setMap(DOUBLE, 		  		Double.class);
		setMap(FLOAT,	 		  		Float.class);
		setMap(INTEGER, 		  		Integer.class);
		setMap(BIGINT, 		  		Long.class);
		setMap(SMALLINT, 		  		Short.class);
		setMap(SQL_TIMESTAMP,  		java.sql.Timestamp.class);
		setMap(SQL_DATE, 				java.sql.Date.class);
		setMap(SQL_TIME, 			   java.sql.Time.class);
		setMap(VARBINARY, 	  		byte[].class);
		setMap(NULL, 			  		null);
		setMap(JAVA_OBJECT,    		Object.class);		
		setMap(P__BOOLEAN, 	  		boolean.class);
		setMap(P__TINYINT, 	 		byte.class);
		setMap(P__CHAR, 		  		char.class);
		setMap(P__DOUBLE, 		  	double.class);
		setMap(P__FLOAT, 		  		float.class);
		setMap(P__INTEGER, 	  		int.class);
		setMap(P__BIGINT, 		  	long.class);
		setMap(P__SMALLINT, 	  		short.class);
						
		//	sqlIntToSqlTypeNames	
		setMap(java.sql.Types.VARCHAR,  	  SQL__VARCHAR);
		setMap(java.sql.Types.BOOLEAN,  	  SQL__BOOLEAN);
		setMap(java.sql.Types.TINYINT,  	  SQL__TINYINT);
		setMap(java.sql.Types.CHAR, 	  	  SQL__CHAR);
		setMap(java.sql.Types.DOUBLE,   	  SQL__DOUBLE);
		setMap(java.sql.Types.FLOAT, 	  	  SQL__FLOAT);
		setMap(java.sql.Types.INTEGER,  	  SQL__INTEGER);
		setMap(java.sql.Types.BIGINT,      SQL__BIGINT);
		setMap(java.sql.Types.SMALLINT,    SQL__SMALLINT);
		setMap(java.sql.Types.TIMESTAMP,   SQL__TIMESTAMP);
		setMap(java.sql.Types.DATE, 		  SQL__DATE);		
		setMap(java.sql.Types.TIME,	 	  SQL__TIME);		
		setMap(java.sql.Types.VARBINARY,   SQL__VARBINARY);
		setMap(java.sql.Types.NULL, 		  SQL__NULL);
		setMap(java.sql.Types.JAVA_OBJECT, SQL__JAVA_OBJECT);						
		setMap(P_BOOLEAN, 					  P__BOOLEAN);   	 	
		setMap(P_TINYINT, 					  P__TINYINT); 
		setMap(P_CHAR, 						  P__CHAR);
		setMap(P_DOUBLE, 						  P__DOUBLE); 
		setMap(P_FLOAT, 						  P__FLOAT);
		setMap(P_INTEGER, 					  P__INTEGER); 
		setMap(P_BIGINT, 						  P__BIGINT); 
		setMap(P_SMALLINT, 					  P__SMALLINT);
		
	}
	

	public static void setStringToTypes(Map namesToJavaTypes) 
	throws SQLException{
		if(namesToJavaTypes != null){
			Collection t = namesToJavaTypes.values();
			Iterator i = t.iterator();
			Class ct;
			while(i.hasNext()){
				ct = (Class)i.next();
				if(!sqlTypesToInt.containsKey(ct))
					throw new SQLException("Unsupported type (" + ct.getName() + ")");
			}
		}
		sqlStringToTypes = new HashMap(namesToJavaTypes);		
	}
	

	/*
	 * @Return null if type is not a valid SQL type 
	 * (please note that BIT NUMERIC DECIMAL OTHER JAVA_OBJECT DISTINCT STRUCT ARRAY BLOB CLOB REF DATALINK are not handled
	 **/
	public static Class getAvalidSqlType(int type){
		return (Class)sqlIntToTypes.get(new Integer(type));
	}
	
	
	public static Class getAvalidSqlType(Integer type){
		return (Class)sqlIntToTypes.get(type);
	}
	

	public static Class getAvalidSqlType(String typeIdentifier){
		if(isAnumericallySpecifiedValidType(typeIdentifier))
			return getAvalidSqlType(Integer.parseInt(typeIdentifier));
		else if(isAtypeNameSpecifidValidType(typeIdentifier))
			return (Class)sqlStringToTypes.get(typeIdentifier);
		else
			return null;
	}

	
	public static String getAvalidSqlTypeName(int type){
		return (String)sqlIntToSqlTypeNames.get(new Integer(type));
	}
	
	
	public static String getAvalidSqlTypeName(Integer type){
		return (String)sqlIntToSqlTypeNames.get(type);
	}
	
	public static String getAvalidSqlTypeName(Class type) {
		int typ = getJavaSqlTypesToSqlInt(type);
		return getAvalidSqlTypeName(typ);
	}
	
		
	/**
	 * @param typeIdentifier
	 * @return
	 */
	private static boolean isAtypeNameSpecifidValidType(String typeIdentifier) {
		boolean result;
		//
		result = true;			
		if(!sqlStringToTypes.containsKey(typeIdentifier))
			result = false;		
		return result;
	}


	/**
	 * @param typeIdentifier
	 * @return
	 */
	private static boolean isAnumericallySpecifiedValidType(String typeIdentifier) {					
		if(!StringUtilities.isInteger(typeIdentifier))
			return false;
		if(!sqlIntToTypes.containsKey(Integer.valueOf(typeIdentifier)))
			return false;
		return true;
	}

								
	public static int getJavaSqlTypesToSqlInt(Class type) {
		return ((Integer)sqlTypesToInt.get(type)).intValue();
	}
	
	
	public static Integer getJavaSqlTypesToSqlInteger(Class type) {
		return (Integer)sqlTypesToInt.get(type);
	}
			
}
